#include <partical/ParticleSystem.h>

namespace ne
{
    RadixSort32<ParticleList, Particle*, float> ParticleSystem::RadixSorter;

    const real ParticleSystem::DEFAULT_TICK_STEP = 0.05f;

    ParticleSystem::ParticleSystem()
        : Node(),
          mSpeed(1.0f),
          mSortEnable(false),
          mSortMode(SM_DIRECTION),
          mEmitting(false),
          mPausing(false),
          mNeedUpdateAABB(true)
    {
    }

    void ParticleSystem::setSpeed(const real speed)
    {
        mSpeed = speed;
    }

    real ParticleSystem::getSpeed() const
    {
        return mSpeed;
    }

    void ParticleSystem::setSortEnable(const bool b)
    {
        mSortEnable = b;
    }

    bool ParticleSystem::isSortEnable() const
    {
        return mSortEnable;
    }

    void ParticleSystem::setSortMode(const SortMode &sm)
    {
        mSortMode = sm;
    }

    SortMode ParticleSystem::getSortMode() const
    {
        return mSortMode;
    }

    void ParticleSystem::setParticlePoolSize(const size_t size)
    {
        if (size > mParticlePool.size())
        {
            _increaseParticlePool(size);
        }
    }

    size_t ParticleSystem::getParticlePoolSize() const
    {
        return mParticlePool.size();
    }

    Particle* ParticleSystem::createParticle()
    {
        if (!mFreeParticles.empty())
        {
            Particle *pParticle = mFreeParticles.front();
            mFreeParticles.pop_front();
            mActiveParticles.push_back(pParticle);
            return pParticle;
        }
        return 0;
    }

    size_t ParticleSystem::getParticleNum() const
    {
        return mActiveParticles.size();
    }

    Particle* ParticleSystem::getParticle(const size_t index)
    {
        assert(index < mActiveParticles.size());
        ParticleListIterator it = mActiveParticles.begin();
        std::advance(it, index);
        return *it;
    }

    ParticleList& ParticleSystem::getParticleList()
    {
        return mActiveParticles;
    }

    void ParticleSystem::clearParticles()
    {
        mFreeParticles.splice(mFreeParticles.end(), mActiveParticles);
    }

    ParticleEmitter* ParticleSystem::createEmittedEmitter()
    {
        if (!mFreeEmittedEmitters.empty())
        {
            ParticleEmitter *pEmitter = mFreeEmittedEmitters.front();
            mFreeEmittedEmitters.pop_front();
            mActiveEmittedEmitters.push_back(pEmitter);
            return pEmitter;
        }
        return 0;
    }

    size_t ParticleSystem::getEmittedEmitterNum() const
    {
        return mActiveEmittedEmitters.size();
    }

    ParticleEmitter* ParticleSystem::getEmittedEmitter(const size_t index)
    {
        assert(index < mActiveEmittedEmitters.size());
        ParticleEmitterListIterator it = mActiveEmittedEmitters.begin();
        std::advance(it, index);
        return *it;
    }

    ParticleEmitterList& ParticleSystem::getEmittedEmitterList()
    {
        return mActiveEmittedEmitters;
    }

    void ParticleSystem::clearEmittedEmitters()
    {
        mFreeEmittedEmitters.splice(mFreeEmittedEmitters.end(), mActiveEmittedEmitters);
    }

    ParticleEmitter* ParticleSystem::createEmitter(const std::string &type)
    {
        ParticleEmitterMapIterator it = mEmitters.find(type);
        if (it == mEmitters.end())
        {
            ParticleEmitter *pEmitter = new ParticleEmitter(type);
            mEmitters[type] = pEmitter;
            return pEmitter;
        }
        return it->second;
    }

    ParticleEmitter* ParticleSystem::getEmitter(const std::string &type)
    {
        ParticleEmitterMapIterator it = mEmitters.find(type);
        if (it != mEmitters.end())
        {
            return it->second;
        }
        return 0;
    }

    void ParticleSystem::destroyEmitter(const std::string &type)
    {
        ParticleEmitterMapIterator it = mEmitters.find(type);
        if (it != mEmitters.end())
        {
            delete it->second;
            mEmitters.erase(it);
        }
    }

    void ParticleSystem::destroyEmitters()
    {
        for (ParticleEmitterMapIterator it=mEmitters.begin(); it!=mEmitters.end(); ++it)
        {
            delete it->second;
        }
        mEmitters.clear();
    }

    size_t ParticleSystem::getEmitterNum() const
    {
        return mEmitters.size();
    }

    ParticleAffector* ParticleSystem::createAffector(const std::string &type)
    {
        ParticleAffector *pAffector = 0;
        if (type == "ParticleFadeAffector")
        {
            pAffector = new ParticleFadeAffector();
        }
        else if (type == "ParticleGravityAffector")
        {
            pAffector = new ParticleGravityAffector();
        }
        else
        {
            return 0;
        }
        mAffectors.push_back(pAffector);
        return pAffector;
    }

    ParticleAffector* ParticleSystem::getAffector(const size_t index)
    {
        if (index < mAffectors.size())
        {
            return mAffectors[index];
        }
        return 0;
    }

    void ParticleSystem::destroyAffector(const size_t index)
    {
        if (index < mAffectors.size())
        {
            ParticleAffectorVectorIterator it = mAffectors.begin();
            it += index;
            delete (*it);
            mAffectors.erase(it);
        }
    }

    void ParticleSystem::destroyAffectors()
    {
        for (ParticleAffectorVectorIterator it=mAffectors.begin(); it!=mAffectors.end(); ++it)
        {
            delete (*it);
        }
        mAffectors.clear();
    }

    size_t ParticleSystem::getAffectorNum() const
    {
        return mAffectors.size();
    }

    const AABB& ParticleSystem::getAABB() const
    {
        _updateAABB();
        return mAABB;
    }

    void ParticleSystem::play()
    {
        mEmitting = true;
        mPausing = false;
    }

    void ParticleSystem::pause(const bool b)
    {
        mPausing = b;
    }

    // TODO: need to fix
    void ParticleSystem::stop(const bool force)
    {
        mEmitting = false;
        mPausing = false;
        if (force)
        {
            clearParticles();
        }
    }

    bool ParticleSystem::isPlaying() const
    {
        return (mEmitting && !mPausing);
    }

    bool ParticleSystem::isPausing() const
    {
        return mPausing;
    }

    void ParticleSystem::tick(const real interval, const real step)
    {
        real dT = interval * mSpeed;

        if (!mPausing)
        {
            while (dT > step)
            {
                _expireParticles(step);
                _expireEmittedEmitters(step);
                _triggerAffectors(step);
                _applyMotion(step);
                if (mEmitting)
                {
                    _triggerEmitters(step);
                }
                dT -= step;
            }

            if (dT > 0.0f)
            {
                _expireParticles(dT);
                _expireEmittedEmitters(dT);
                _triggerAffectors(dT);
                _applyMotion(dT);
                if (mEmitting)
                {
                    _triggerEmitters(dT);
                }
            }

            needUpdateAABB();
        }
    }

    void ParticleSystem::updateParticles(const Camera *pCamera)
    {
        if (mSortEnable)
        {
            _sortParticles(pCamera);
        }
    }

    // TODO: Need to fix
    void ParticleSystem::update()
    {
        Node::update();
        updateAABB();
    }

    void ParticleSystem::needUpdate(const bool forceParentUpdate)
    {
        Node::needUpdate(forceParentUpdate);
        needUpdateAABB();
    }

    void ParticleSystem::needUpdateAABB()
    {
        mNeedUpdateAABB = true;
    }

    void ParticleSystem::updateAABB()
    {
        _updateAABB();
    }

    void ParticleSystem::clear()
    {
        clearEmittedEmitters();
        clearParticles();
    }

    ParticleSystem::~ParticleSystem()
    {
        destroyAffectors();
        destroyEmitters();
        _destroyEmittedEmitterPool();
        _destroyParticlePool();
    }

    void ParticleSystem::_increaseParticlePool(const size_t size)
    {
        size_t oldSize = mParticlePool.size();
        if (oldSize < size)
        {
            mParticlePool.reserve(size);
            mParticlePool.resize(size);
            for (size_t i=oldSize; i<size; ++i)
            {
                mParticlePool[i] = new Particle();
                mFreeParticles.push_back(mParticlePool[i]);
            }
        }
    }

    void ParticleSystem::_increaseEmittedEmitterPool(const size_t size)
    {
        size_t oldSize = mEmittedEmitterPool.size();
        if (oldSize < size)
        {
            mEmittedEmitterPool.reserve(size);
            mEmittedEmitterPool.resize(size);
            for (size_t i=oldSize; i<size; ++i)
            {
                mEmittedEmitterPool[i] = new ParticleEmitter("ParticleEmitter");
                mFreeEmittedEmitters.push_back(mEmittedEmitterPool[i]);
            }
        }
    }

    void ParticleSystem::_destroyParticlePool()
    {
        mActiveParticles.clear();
        mFreeParticles.clear();
        for (ParticleVectorIterator it=mParticlePool.begin(); it!=mParticlePool.end(); ++it)
        {
            delete (*it);
        }
        mParticlePool.clear();
    }

    void ParticleSystem::_destroyEmittedEmitterPool()
    {
        mActiveEmittedEmitters.clear();
        mFreeEmittedEmitters.clear();
        for (ParticleEmitterVectorIterator it=mEmittedEmitterPool.begin(); it!=mEmittedEmitterPool.end(); ++it)
        {
            delete (*it);
        }
        mEmittedEmitterPool.clear();
    }

    void ParticleSystem::_expireParticles(const real interval)
    {
        ParticleListIterator it = mActiveParticles.begin();
        while (it != mActiveParticles.end())
        {
            Particle *pParticle = *it;
            pParticle->lifetime += interval;
            pParticle->force = Vector3::ZERO;
            if (pParticle->isDead())
            {
                it = mActiveParticles.erase(it);
                mFreeParticles.push_back(pParticle);
            }
            else
            {
                ++it;
            }
        }
    }

    void ParticleSystem::_expireEmittedEmitters(const real interval)
    {
        ParticleEmitterListIterator it = mActiveEmittedEmitters.begin();
        while (it != mActiveEmittedEmitters.end())
        {
            ParticleEmitter *pEmitter = *it;
            pEmitter->lifetime += interval;
            pEmitter->force = Vector3::ZERO;
            if (pEmitter->isDead())
            {
                pEmitter->disable();
                it = mActiveEmittedEmitters.erase(it);
                mFreeEmittedEmitters.push_back(pEmitter);
            }
            else
            {
                ++it;
            }
        }
    }

    void ParticleSystem::_triggerAffectors(const real interval)
    {
        for (ParticleAffectorVectorIterator it=mAffectors.begin(); it!=mAffectors.end(); ++it)
        {
            (*it)->affectParticles(mActiveParticles, interval);
        }
    }

    void ParticleSystem::_applyMotion(const real interval)
    {
        for (ParticleListIterator it=mActiveParticles.begin(); it!=mActiveParticles.end(); ++it)
        {
            Particle *pParticle = *it;
            Vector3 acceleration = pParticle->force / pParticle->mass;
            pParticle->velocity += acceleration * interval;
            pParticle->direction = pParticle->velocity.getUnit();

            pParticle->position += pParticle->velocity * interval;
            pParticle->scale += pParticle->zoom * interval;
        }
    }

    void ParticleSystem::_triggerEmitters(const real interval)
    {
        /* Active emitted emitters
        size_t emittedCount = 0;
        size_t activeEmitterCount = mActiveEmittedEmitters.size();
        std::vector<size_t> emittedCountList(activeEmitterCount);
        ParticleEmitterListIterator it = mActiveEmittedEmitters.begin();
        for (size_t i=0; it!=mActiveEmittedEmitters.end(); ++i, ++it)
        {
            size_t n = (*it)->genEmissionParticleCount(interval);
            emittedCount += n;
            emittedCountList[i] = n;
        }

        if (emittedCount + mActiveParticles.size() > mParticlePool.size())
        {
            _increaseParticlePool(emittedCount + mActiveParticles.size());
        }

        it = mActiveEmittedEmitters.begin();
        for (size_t i=0; it!=mActiveEmittedEmitters.end(); ++i, ++it)
        {
            for (size_t j=0; j<emittedCountList[i]; ++j)
            {
                Particle *pParticle = createParticle();
                (*it)->initialParticle(pParticle);
            }
        }
        */
        size_t emittedCount = 0;
        size_t emitterCount = mEmitters.size();
        std::vector<size_t> emittedCountList(emitterCount);
        ParticleEmitterMapIterator it = mEmitters.begin();
        for (size_t i=0; it!=mEmitters.end(); ++i, ++it)
        {
            size_t n = it->second->genEmissionParticleCount(interval);
            emittedCount += n;
            emittedCountList[i] = n;
        }

        if (emittedCount + mActiveParticles.size() > mParticlePool.size())
        {
            _increaseParticlePool(emittedCount + mActiveParticles.size());
        }

        it = mEmitters.begin();
        for (size_t i=0; it!=mEmitters.end(); ++i, ++it)
        {
            for (size_t j=0; j<emittedCountList[i]; ++j)
            {
                Particle *pParticle = createParticle();
                it->second->initialParticle(pParticle);
            }
        }
    }

    void ParticleSystem::_sortParticles(const Camera *pCamera)
    {
        if (pCamera)
        {
            switch (getSortMode())
            {
                case SM_DISTANCE:
                    ParticleSystem::RadixSorter.sort(mActiveParticles, SortByDistanceFunctor(pCamera->getPosition(TS_WORLD)));
                    break;

                case SM_DIRECTION:
                    ParticleSystem::RadixSorter.sort(mActiveParticles, SortByDirectionFunctor(pCamera->getBack()));
                    break;

                default: break;
            }
        }
    }

    void ParticleSystem::_updateAABB() const
    {
        if (mNeedUpdateAABB)
        {
            mAABB.setExtent(AABB_EXTENT_NULL);
            
            for (ParticleListConstIterator it=mActiveParticles.begin(); it!=mActiveParticles.end(); ++it)
            {
                Particle *pParticle = *it;
                mAABB.merge(pParticle->position);
            }
            mNeedUpdateAABB = false;
        }
    }
}
